define(function(require, exports, module) {

  // var Backbone = require('backbone');
  var $ = require('jquery');
  var _ = require('underscore');
  // var jsonview = require('jsonview');
  var tmpl = require('text!../tmpl.html');

  var d3 = require('d3v3')
  require('d3.bihisankey')

  function main() {
    var $app = $('<div id="app">');
    $('#main').html($app);

    $app.append(tmpl);

    var svg, tooltip, biHiSankey, path, defs, colorScale, highlightColorScale, isTransitioning;

    var OPACITY = {
        NODE_DEFAULT: 0.9,
        NODE_FADED: 0.1,
        NODE_HIGHLIGHT: 0.8,
        LINK_DEFAULT: 0.6,
        LINK_FADED: 0.05,
        LINK_HIGHLIGHT: 0.9
      },
      TYPES = ["Asset", "Expense", "Revenue", "Equity", "Liability"],
      TYPE_COLORS = ["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02", "#a6761d"],
      TYPE_HIGHLIGHT_COLORS = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494"],
      LINK_COLOR = "#b3b3b3",
      INFLOW_COLOR = "#2E86D1",
      OUTFLOW_COLOR = "#D63028",
      NODE_WIDTH = 36,
      COLLAPSER = {
        RADIUS: NODE_WIDTH / 2,
        SPACING: 2
      },
      OUTER_MARGIN = 10,
      MARGIN = {
        TOP: 2 * (COLLAPSER.RADIUS + OUTER_MARGIN),
        RIGHT: OUTER_MARGIN,
        BOTTOM: OUTER_MARGIN,
        LEFT: OUTER_MARGIN
      },
      TRANSITION_DURATION = 400,
      HEIGHT = 500 - MARGIN.TOP - MARGIN.BOTTOM,
      WIDTH = 960 - MARGIN.LEFT - MARGIN.RIGHT,
      LAYOUT_INTERATIONS = 32,
      REFRESH_INTERVAL = 7000;

    var formatNumber = function(d) {
        var numberFormat = d3.format(",.0f"); // zero decimal places
        return "£" + numberFormat(d);
      },

      formatFlow = function(d) {
        var flowFormat = d3.format(",.0f"); // zero decimal places with sign
        return "£" + flowFormat(Math.abs(d)) + (d < 0 ? " CR" : " DR");
      },

      // Used when temporarily disabling user interractions to allow animations to complete
      disableUserInterractions = function(time) {
        isTransitioning = true;
        setTimeout(function() {
          isTransitioning = false;
        }, time);
      },

      hideTooltip = function() {
        return tooltip.transition()
          .duration(TRANSITION_DURATION)
          .style("opacity", 0);
      },

      showTooltip = function() {
        return tooltip
          .style("left", d3.event.pageX + "px")
          .style("top", d3.event.pageY + 15 + "px")
          .transition()
          .duration(TRANSITION_DURATION)
          .style("opacity", 1);
      };

    colorScale = d3.scale.ordinal().domain(TYPES).range(TYPE_COLORS),
      highlightColorScale = d3.scale.ordinal().domain(TYPES).range(TYPE_HIGHLIGHT_COLORS),

      svg = d3.select("#chart").append("svg")
      .attr("width", WIDTH + MARGIN.LEFT + MARGIN.RIGHT)
      .attr("height", HEIGHT + MARGIN.TOP + MARGIN.BOTTOM)
      .append("g")
      .attr("transform", "translate(" + MARGIN.LEFT + "," + MARGIN.TOP + ")");

    svg.append("g").attr("id", "links");
    svg.append("g").attr("id", "nodes");
    svg.append("g").attr("id", "collapsers");

    tooltip = d3.select("#chart").append("div").attr("id", "tooltip");

    tooltip.style("opacity", 0)
      .append("p")
      .attr("class", "value");

    biHiSankey = d3.biHiSankey();

    // Set the biHiSankey diagram properties
    biHiSankey
      .nodeWidth(NODE_WIDTH)
      .nodeSpacing(10)
      .linkSpacing(4)
      .arrowheadScaleFactor(0.5) // Specifies that 0.5 of the link's stroke WIDTH should be allowed for the marker at the end of the link.
      .size([WIDTH, HEIGHT]);

    path = biHiSankey.link().curvature(0.45);

    defs = svg.append("defs");

    defs.append("marker")
      .style("fill", LINK_COLOR)
      .attr("id", "arrowHead")
      .attr("viewBox", "0 0 6 10")
      .attr("refX", "1")
      .attr("refY", "5")
      .attr("markerUnits", "strokeWidth")
      .attr("markerWidth", "1")
      .attr("markerHeight", "1")
      .attr("orient", "auto")
      .append("path")
      .attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");

    defs.append("marker")
      .style("fill", OUTFLOW_COLOR)
      .attr("id", "arrowHeadInflow")
      .attr("viewBox", "0 0 6 10")
      .attr("refX", "1")
      .attr("refY", "5")
      .attr("markerUnits", "strokeWidth")
      .attr("markerWidth", "1")
      .attr("markerHeight", "1")
      .attr("orient", "auto")
      .append("path")
      .attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");

    defs.append("marker")
      .style("fill", INFLOW_COLOR)
      .attr("id", "arrowHeadOutlow")
      .attr("viewBox", "0 0 6 10")
      .attr("refX", "1")
      .attr("refY", "5")
      .attr("markerUnits", "strokeWidth")
      .attr("markerWidth", "1")
      .attr("markerHeight", "1")
      .attr("orient", "auto")
      .append("path")
      .attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");

    function update() {
      var link, linkEnter, node, nodeEnter, collapser, collapserEnter;

      function dragmove(node) {
        node.x = Math.max(0, Math.min(WIDTH - node.width, d3.event.x));
        node.y = Math.max(0, Math.min(HEIGHT - node.height, d3.event.y));
        d3.select(this).attr("transform", "translate(" + node.x + "," + node.y + ")");
        biHiSankey.relayout();
        svg.selectAll(".node").selectAll("rect").attr("height", function(d) {
          return d.height; });
        link.attr("d", path);
      }

      function containChildren(node) {
        node.children.forEach(function(child) {
          child.state = "contained";
          child.parent = this;
          child._parent = null;
          containChildren(child);
        }, node);
      }

      function expand(node) {
        node.state = "expanded";
        node.children.forEach(function(child) {
          child.state = "collapsed";
          child._parent = this;
          child.parent = null;
          containChildren(child);
        }, node);
      }

      function collapse(node) {
        node.state = "collapsed";
        containChildren(node);
      }

      function restoreLinksAndNodes() {
        link
          .style("stroke", LINK_COLOR)
          .style("marker-end", function() {
            return 'url(#arrowHead)'; })
          .transition()
          .duration(TRANSITION_DURATION)
          .style("opacity", OPACITY.LINK_DEFAULT);

        node
          .selectAll("rect")
          .style("fill", function(d) {
            d.color = colorScale(d.type.replace(/ .*/, ""));
            return d.color;
          })
          .style("stroke", function(d) {
            return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
          })
          .style("fill-opacity", OPACITY.NODE_DEFAULT);

        node.filter(function(n) {
            return n.state === "collapsed"; })
          .transition()
          .duration(TRANSITION_DURATION)
          .style("opacity", OPACITY.NODE_DEFAULT);
      }

      function showHideChildren(node) {
        disableUserInterractions(2 * TRANSITION_DURATION);
        hideTooltip();
        if (node.state === "collapsed") { expand(node); } else { collapse(node); }

        biHiSankey.relayout();
        update();
        link.attr("d", path);
        restoreLinksAndNodes();
      }

      function highlightConnected(g) {
        link.filter(function(d) {
            return d.source === g; })
          .style("marker-end", function() {
            return 'url(#arrowHeadInflow)'; })
          .style("stroke", OUTFLOW_COLOR)
          .style("opacity", OPACITY.LINK_DEFAULT);

        link.filter(function(d) {
            return d.target === g; })
          .style("marker-end", function() {
            return 'url(#arrowHeadOutlow)'; })
          .style("stroke", INFLOW_COLOR)
          .style("opacity", OPACITY.LINK_DEFAULT);
      }

      function fadeUnconnected(g) {
        link.filter(function(d) {
            return d.source !== g && d.target !== g; })
          .style("marker-end", function() {
            return 'url(#arrowHead)'; })
          .transition()
          .duration(TRANSITION_DURATION)
          .style("opacity", OPACITY.LINK_FADED);

        node.filter(function(d) {
            return (d.name === g.name) ? false : !biHiSankey.connected(d, g);
          }).transition()
          .duration(TRANSITION_DURATION)
          .style("opacity", OPACITY.NODE_FADED);
      }

      link = svg.select("#links").selectAll("path.link")
        .data(biHiSankey.visibleLinks(), function(d) {
          return d.id; });

      link.transition()
        .duration(TRANSITION_DURATION)
        .style("stroke-WIDTH", function(d) {
          return Math.max(1, d.thickness); })
        .attr("d", path)
        .style("opacity", OPACITY.LINK_DEFAULT);


      link.exit().remove();


      linkEnter = link.enter().append("path")
        .attr("class", "link")
        .style("fill", "none");

      linkEnter.on('mouseenter', function(d) {
        if (!isTransitioning) {
          showTooltip().select(".value").text(function() {
            if (d.direction > 0) {
              return d.source.name + " → " + d.target.name + "\n" + formatNumber(d.value);
            }
            return d.target.name + " ← " + d.source.name + "\n" + formatNumber(d.value);
          });

          d3.select(this)
            .style("stroke", LINK_COLOR)
            .transition()
            .duration(TRANSITION_DURATION / 2)
            .style("opacity", OPACITY.LINK_HIGHLIGHT);
        }
      });

      linkEnter.on('mouseleave', function() {
        if (!isTransitioning) {
          hideTooltip();

          d3.select(this)
            .style("stroke", LINK_COLOR)
            .transition()
            .duration(TRANSITION_DURATION / 2)
            .style("opacity", OPACITY.LINK_DEFAULT);
        }
      });

      linkEnter.sort(function(a, b) {
          return b.thickness - a.thickness; })
        .classed("leftToRight", function(d) {
          return d.direction > 0;
        })
        .classed("rightToLeft", function(d) {
          return d.direction < 0;
        })
        .style("marker-end", function() {
          return 'url(#arrowHead)';
        })
        .style("stroke", LINK_COLOR)
        .style("opacity", 0)
        .transition()
        .delay(TRANSITION_DURATION)
        .duration(TRANSITION_DURATION)
        .attr("d", path)
        .style("stroke-WIDTH", function(d) {
          return Math.max(1, d.thickness); })
        .style("opacity", OPACITY.LINK_DEFAULT);


      node = svg.select("#nodes").selectAll(".node")
        .data(biHiSankey.collapsedNodes(), function(d) {
          return d.id; });


      node.transition()
        .duration(TRANSITION_DURATION)
        .attr("transform", function(d) {
          return "translate(" + d.x + "," + d.y + ")"; })
        .style("opacity", OPACITY.NODE_DEFAULT)
        .select("rect")
        .style("fill", function(d) {
          d.color = colorScale(d.type.replace(/ .*/, ""));
          return d.color;
        })
        .style("stroke", function(d) {
          return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1); })
        .style("stroke-WIDTH", "1px")
        .attr("height", function(d) {
          return d.height; })
        .attr("width", biHiSankey.nodeWidth());


      node.exit()
        .transition()
        .duration(TRANSITION_DURATION)
        .attr("transform", function(d) {
          var collapsedAncestor, endX, endY;
          collapsedAncestor = d.ancestors.filter(function(a) {
            return a.state === "collapsed";
          })[0];
          endX = collapsedAncestor ? collapsedAncestor.x : d.x;
          endY = collapsedAncestor ? collapsedAncestor.y : d.y;
          return "translate(" + endX + "," + endY + ")";
        })
        .remove();


      nodeEnter = node.enter().append("g").attr("class", "node");

      nodeEnter
        .attr("transform", function(d) {
          var startX = d._parent ? d._parent.x : d.x,
            startY = d._parent ? d._parent.y : d.y;
          return "translate(" + startX + "," + startY + ")";
        })
        .style("opacity", 1e-6)
        .transition()
        .duration(TRANSITION_DURATION)
        .style("opacity", OPACITY.NODE_DEFAULT)
        .attr("transform", function(d) {
          return "translate(" + d.x + "," + d.y + ")"; });

      nodeEnter.append("text");
      nodeEnter.append("rect")
        .style("fill", function(d) {
          d.color = colorScale(d.type.replace(/ .*/, ""));
          return d.color;
        })
        .style("stroke", function(d) {
          return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
        })
        .style("stroke-WIDTH", "1px")
        .attr("height", function(d) {
          return d.height; })
        .attr("width", biHiSankey.nodeWidth());

      node.on("mouseenter", function(g) {
        if (!isTransitioning) {
          restoreLinksAndNodes();
          highlightConnected(g);
          fadeUnconnected(g);

          d3.select(this).select("rect")
            .style("fill", function(d) {
              d.color = d.netFlow > 0 ? INFLOW_COLOR : OUTFLOW_COLOR;
              return d.color;
            })
            .style("stroke", function(d) {
              return d3.rgb(d.color).darker(0.1);
            })
            .style("fill-opacity", OPACITY.LINK_DEFAULT);

          tooltip
            .style("left", g.x + MARGIN.LEFT + "px")
            .style("top", g.y + g.height + MARGIN.TOP + 15 + "px")
            .transition()
            .duration(TRANSITION_DURATION)
            .style("opacity", 1).select(".value")
            .text(function() {
              var additionalInstructions = g.children.length ? "\n(Double click to expand)" : "";
              return g.name + "\nNet flow: " + formatFlow(g.netFlow) + additionalInstructions;
            });
        }
      });

      node.on("mouseleave", function() {
        if (!isTransitioning) {
          hideTooltip();
          restoreLinksAndNodes();
        }
      });

      node.filter(function(d) {
          return d.children.length; })
        .on("dblclick", showHideChildren);

      // allow nodes to be dragged to new positions
      node.call(d3.behavior.drag()
        .origin(function(d) {
          return d; })
        .on("dragstart", function() { this.parentNode.appendChild(this); })
        .on("drag", dragmove));

      // add in the text for the nodes
      node.filter(function(d) {
          return d.value !== 0; })
        .select("text")
        .attr("x", -6)
        .attr("y", function(d) {
          return d.height / 2; })
        .attr("dy", ".35em")
        .attr("text-anchor", "end")
        .attr("transform", null)
        .text(function(d) {
          return d.name; })
        .filter(function(d) {
          return d.x < WIDTH / 2; })
        .attr("x", 6 + biHiSankey.nodeWidth())
        .attr("text-anchor", "start");


      collapser = svg.select("#collapsers").selectAll(".collapser")
        .data(biHiSankey.expandedNodes(), function(d) {
          return d.id; });


      collapserEnter = collapser.enter().append("g").attr("class", "collapser");

      collapserEnter.append("circle")
        .attr("r", COLLAPSER.RADIUS)
        .style("fill", function(d) {
          d.color = colorScale(d.type.replace(/ .*/, ""));
          return d.color;
        });

      collapserEnter
        .style("opacity", OPACITY.NODE_DEFAULT)
        .attr("transform", function(d) {
          return "translate(" + (d.x + d.width / 2) + "," + (d.y + COLLAPSER.RADIUS) + ")";
        });

      collapserEnter.on("dblclick", showHideChildren);

      collapser.select("circle")
        .attr("r", COLLAPSER.RADIUS);

      collapser.transition()
        .delay(TRANSITION_DURATION)
        .duration(TRANSITION_DURATION)
        .attr("transform", function(d, i) {
          return "translate(" + (COLLAPSER.RADIUS + i * 2 * (COLLAPSER.RADIUS + COLLAPSER.SPACING)) + "," + (-COLLAPSER.RADIUS - OUTER_MARGIN) + ")";
        });

      collapser.on("mouseenter", function(g) {
        if (!isTransitioning) {
          showTooltip().select(".value")
            .text(function() {
              return g.name + "\n(Double click to collapse)";
            });

          var highlightColor = highlightColorScale(g.type.replace(/ .*/, ""));

          d3.select(this)
            .style("opacity", OPACITY.NODE_HIGHLIGHT)
            .select("circle")
            .style("fill", highlightColor);

          node.filter(function(d) {
              return d.ancestors.indexOf(g) >= 0;
            }).style("opacity", OPACITY.NODE_HIGHLIGHT)
            .select("rect")
            .style("fill", highlightColor);
        }
      });

      collapser.on("mouseleave", function(g) {
        if (!isTransitioning) {
          hideTooltip();
          d3.select(this)
            .style("opacity", OPACITY.NODE_DEFAULT)
            .select("circle")
            .style("fill", function(d) {
              return d.color; });

          node.filter(function(d) {
              return d.ancestors.indexOf(g) >= 0;
            }).style("opacity", OPACITY.NODE_DEFAULT)
            .select("rect")
            .style("fill", function(d) {
              return d.color; });
        }
      });

      collapser.exit().remove();

    }

    var exampleNodes = [
      { "type": "Asset", "id": "a", "parent": null, "name": "Assets" },
      { "type": "Asset", "id": 1, "parent": "a", "number": "101", "name": "Cash" },
      { "type": "Asset", "id": 2, "parent": "a", "number": "120", "name": "Accounts Receivable" },
      { "type": "Asset", "id": 3, "parent": "a", "number": "140", "name": "Merchandise Inventory" },
      { "type": "Asset", "id": 4, "parent": "a", "number": "150", "name": "Supplies" },
      { "type": "Asset", "id": 5, "parent": "a", "number": "160", "name": "Prepaid Insurance" },
      { "type": "Asset", "id": 6, "parent": "a", "number": "170", "name": "Land" },
      { "type": "Asset", "id": 7, "parent": "a", "number": "175", "name": "Buildings" },
      { "type": "Asset", "id": 8, "parent": "a", "number": "178", "name": "Acc. Depreciation Buildings" },
      { "type": "Asset", "id": 9, "parent": "a", "number": "180", "name": "Equipment" },
      { "type": "Asset", "id": 10, "parent": "a", "number": "188", "name": "Acc. Depreciation Equipment" },
      { "type": "Liability", "id": "l", "parent": null, "number": "l", "name": "Liabilities" },
      { "type": "Liability", "id": 11, "parent": "l", "number": "210", "name": "Notes Payable" },
      { "type": "Liability", "id": 12, "parent": "l", "number": "215", "name": "Accounts Payable" },
      { "type": "Liability", "id": 13, "parent": "l", "number": "220", "name": "Wages Payable" },
      { "type": "Liability", "id": 14, "parent": "l", "number": "230", "name": "Interest Payable" },
      { "type": "Liability", "id": 15, "parent": "l", "number": "240", "name": "Unearned Revenues" },
      { "type": "Liability", "id": 16, "parent": "l", "number": "250", "name": "Mortage Loan Payable" },
      { "type": "Equity", "id": "eq", "parent": null, "number": "eq", "name": "Equity" },
      { "type": "Revenue", "id": "r", "parent": null, "number": "r", "name": "Revenues" },
      { "type": "Revenue", "id": "or", "parent": "r", "number": "", "name": "Operating Revenue" },
      { "type": "Revenue", "id": 17, "parent": "or", "number": "310", "name": "Service Revenues" },
      { "type": "Revenue", "id": "nor", "parent": "r", "number": "", "name": "Non-Operating Revenue" },
      { "type": "Revenue", "id": 18, "parent": "nor", "number": "810", "name": "Interest Revenues" },
      { "type": "Revenue", "id": 19, "parent": "nor", "number": "910", "name": "Asset Sale Gain" },
      { "type": "Revenue", "id": 20, "parent": "nor", "number": "960", "name": "Asset Sale Loss" },
      { "type": "Expense", "id": "ex", "parent": null, "number": "ex", "name": "Expenses" },
      { "type": "Expense", "id": 21, "parent": "ex", "number": "500", "name": "Salaries Expense" },
      { "type": "Expense", "id": 22, "parent": "ex", "number": "510", "name": "Wages Expense" },
      { "type": "Expense", "id": 23, "parent": "ex", "number": "540", "name": "Supplies Expense" },
      { "type": "Expense", "id": 24, "parent": "ex", "number": "560", "name": "Rent Expense" },
      { "type": "Expense", "id": 25, "parent": "ex", "number": "570", "name": "Utilities Expense" },
      { "type": "Expense", "id": 26, "parent": "ex", "number": "576", "name": "Telephone Expense" },
      { "type": "Expense", "id": 27, "parent": "ex", "number": "610", "name": "Advertising Expense" },
      { "type": "Expense", "id": 28, "parent": "ex", "number": "750", "name": "Depreciation Expense" }
    ]

    var exampleLinks = [
      { "source": 8, "target": 28, "value": Math.floor(Math.random() * 100) },
      { "source": 17, "target": 18, "value": Math.floor(Math.random() * 100) },
      { "source": 22, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 3, "target": 13, "value": Math.floor(Math.random() * 100) },
      { "source": 24, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 5, "target": 4, "value": Math.floor(Math.random() * 100) },
      { "source": 15, "target": 5, "value": Math.floor(Math.random() * 100) },
      { "source": 18, "target": 8, "value": Math.floor(Math.random() * 100) },
      { "source": 3, "target": 20, "value": Math.floor(Math.random() * 100) },
      { "source": 17, "target": 18, "value": Math.floor(Math.random() * 100) },
      { "source": 22, "target": 5, "value": Math.floor(Math.random() * 100) },
      { "source": 4, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 26, "target": 16, "value": Math.floor(Math.random() * 100) },
      { "source": 27, "target": 6, "value": Math.floor(Math.random() * 100) },
      { "source": 23, "target": 4, "value": Math.floor(Math.random() * 100) },
      { "source": 10, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 17, "target": 16, "value": Math.floor(Math.random() * 100) },
      { "source": 5, "target": 12, "value": Math.floor(Math.random() * 100) },
      { "source": 12, "target": 16, "value": Math.floor(Math.random() * 100) },
      { "source": 19, "target": 5, "value": Math.floor(Math.random() * 100) },
      { "source": 15, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 27, "target": 2, "value": Math.floor(Math.random() * 100) },
      { "source": 26, "target": 28, "value": Math.floor(Math.random() * 100) },
      { "source": 22, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 3, "target": 18, "value": Math.floor(Math.random() * 100) },
      { "source": 18, "target": 5, "value": Math.floor(Math.random() * 100) },
      { "source": 25, "target": 28, "value": Math.floor(Math.random() * 100) },
      { "source": 12, "target": 1, "value": Math.floor(Math.random() * 100) },
      { "source": 28, "target": 21, "value": Math.floor(Math.random() * 100) },
      { "source": 9, "target": 16, "value": Math.floor(Math.random() * 100) },
      { "source": 14, "target": 23, "value": Math.floor(Math.random() * 100) },
      { "source": 6, "target": 1, "value": Math.floor(Math.random() * 100) },
      { "source": 9, "target": 15, "value": Math.floor(Math.random() * 100) },
      { "source": 16, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 22, "target": 28, "value": Math.floor(Math.random() * 100) },
      { "source": 8, "target": 21, "value": Math.floor(Math.random() * 100) },
      { "source": 22, "target": 7, "value": Math.floor(Math.random() * 100) },
      { "source": 18, "target": 10, "value": Math.floor(Math.random() * 100) },
      { "source": "eq", "target": 1, "value": Math.floor(Math.random() * 100) },
      { "source": 1, "target": 21, "value": Math.floor(Math.random() * 100) },
      { "source": 1, "target": 24, "value": Math.floor(Math.random() * 100) },
      { "source": 17, "target": 1, "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) },
      { "source": Math.ceil(Math.random() * 28), "target": Math.ceil(Math.random() * 28), "value": Math.floor(Math.random() * 100) }
    ]

    biHiSankey
      .nodes(exampleNodes)
      .links(exampleLinks)
      .initializeNodes(function(node) {
        node.state = node.parent ? "contained" : "collapsed";
      })
      .layout(LAYOUT_INTERATIONS);

    disableUserInterractions(2 * TRANSITION_DURATION);

    update();
  }

  return main;
})
